/*
 * Copyright (c) 2000, 2008, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.management.relation;

import static com.sun.jmx.mbeanserver.Util.cast;
import static com.sun.jmx.defaults.JmxProperties.RELATION_LOGGER;

import com.sun.jmx.mbeanserver.GetPropertyAction;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamField;
import java.security.AccessController;

import java.util.List;
import java.util.Vector;

import javax.management.MBeanServerNotification;

import javax.management.Notification;
import javax.management.NotificationFilterSupport;
import javax.management.ObjectName;

import java.util.List;
import java.util.logging.Level;
import java.util.Vector;

/**
 * Filter for {@link MBeanServerNotification}.
 * This filter filters MBeanServerNotification notifications by
 * selecting the ObjectNames of interest and the operations (registration,
 * unregistration, both) of interest (corresponding to notification
 * types).
 *
 * <p>The <b>serialVersionUID</b> of this class is <code>2605900539589789736L</code>.
 *
 * @since 1.5
 */
@SuppressWarnings("serial")  // serialVersionUID must be constant
public class MBeanServerNotificationFilter extends NotificationFilterSupport {

  // Serialization compatibility stuff:
  // Two serial forms are supported in this class. The selected form depends
  // on system property "jmx.serial.form":
  //  - "1.0" for JMX 1.0
  //  - any other value for JMX 1.1 and higher
  //
  // Serial version for old serial form
  private static final long oldSerialVersionUID = 6001782699077323605L;
  //
  // Serial version for new serial form
  private static final long newSerialVersionUID = 2605900539589789736L;
  //
  // Serializable fields in old serial form
  private static final ObjectStreamField[] oldSerialPersistentFields =
      {
          new ObjectStreamField("mySelectObjNameList", Vector.class),
          new ObjectStreamField("myDeselectObjNameList", Vector.class)
      };
  //
  // Serializable fields in new serial form
  private static final ObjectStreamField[] newSerialPersistentFields =
      {
          new ObjectStreamField("selectedNames", List.class),
          new ObjectStreamField("deselectedNames", List.class)
      };
  //
  // Actual serial version and serial form
  private static final long serialVersionUID;
  /**
   * @serialField selectedNames List List of {@link ObjectName}s of interest <ul>
   * <li><code>null</code> means that all {@link ObjectName}s are implicitly selected (check for
   * explicit deselections)</li> <li>Empty vector means that no {@link ObjectName} is explicitly
   * selected</li> </ul>
   * @serialField deselectedNames List List of {@link ObjectName}s with no interest <ul>
   * <li><code>null</code> means that all {@link ObjectName}s are implicitly deselected (check for
   * explicit selections))</li> <li>Empty vector means that no {@link ObjectName} is explicitly
   * deselected</li> </ul>
   */
  private static final ObjectStreamField[] serialPersistentFields;
  private static boolean compat = false;

  static {
    try {
      GetPropertyAction act = new GetPropertyAction("jmx.serial.form");
      String form = AccessController.doPrivileged(act);
      compat = (form != null && form.equals("1.0"));
    } catch (Exception e) {
      // OK : Too bad, no compat with 1.0
    }
    if (compat) {
      serialPersistentFields = oldSerialPersistentFields;
      serialVersionUID = oldSerialVersionUID;
    } else {
      serialPersistentFields = newSerialPersistentFields;
      serialVersionUID = newSerialVersionUID;
    }
  }
  //
  // END Serialization compatibility stuff

  //
  // Private members
  //

  /**
   * @serial List of {@link ObjectName}s of interest <ul> <li><code>null</code> means that all
   * {@link ObjectName}s are implicitly selected (check for explicit deselections)</li> <li>Empty
   * vector means that no {@link ObjectName} is explicitly selected</li> </ul>
   */
  private List<ObjectName> selectedNames = new Vector<ObjectName>();

  /**
   * @serial List of {@link ObjectName}s with no interest <ul> <li><code>null</code> means that all
   * {@link ObjectName}s are implicitly deselected (check for explicit selections))</li> <li>Empty
   * vector means that no {@link ObjectName} is explicitly deselected</li> </ul>
   */
  private List<ObjectName> deselectedNames = null;

  //
  // Constructor
  //

  /**
   * Creates a filter selecting all MBeanServerNotification notifications for
   * all ObjectNames.
   */
  public MBeanServerNotificationFilter() {

    super();
    RELATION_LOGGER.entering(MBeanServerNotificationFilter.class.getName(),
        "MBeanServerNotificationFilter");

    enableType(MBeanServerNotification.REGISTRATION_NOTIFICATION);
    enableType(MBeanServerNotification.UNREGISTRATION_NOTIFICATION);

    RELATION_LOGGER.exiting(MBeanServerNotificationFilter.class.getName(),
        "MBeanServerNotificationFilter");
    return;
  }

  //
  // Accessors
  //

  /**
   * Disables any MBeanServerNotification (all ObjectNames are
   * deselected).
   */
  public synchronized void disableAllObjectNames() {

    RELATION_LOGGER.entering(MBeanServerNotificationFilter.class.getName(),
        "disableAllObjectNames");

    selectedNames = new Vector<ObjectName>();
    deselectedNames = null;

    RELATION_LOGGER.exiting(MBeanServerNotificationFilter.class.getName(),
        "disableAllObjectNames");
    return;
  }

  /**
   * Disables MBeanServerNotifications concerning given ObjectName.
   *
   * @param objectName ObjectName no longer of interest
   * @throws IllegalArgumentException if the given ObjectName is null
   */
  public synchronized void disableObjectName(ObjectName objectName)
      throws IllegalArgumentException {

    if (objectName == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(MBeanServerNotificationFilter.class.getName(),
        "disableObjectName", objectName);

    // Removes from selected ObjectNames, if present
    if (selectedNames != null) {
      if (selectedNames.size() != 0) {
        selectedNames.remove(objectName);
      }
    }

    // Adds it in deselected ObjectNames
    if (deselectedNames != null) {
      // If all are deselected, no need to do anything :)
      if (!(deselectedNames.contains(objectName))) {
        // ObjectName was not already deselected
        deselectedNames.add(objectName);
      }
    }

    RELATION_LOGGER.exiting(MBeanServerNotificationFilter.class.getName(),
        "disableObjectName");
    return;
  }

  /**
   * Enables all MBeanServerNotifications (all ObjectNames are selected).
   */
  public synchronized void enableAllObjectNames() {

    RELATION_LOGGER.entering(MBeanServerNotificationFilter.class.getName(),
        "enableAllObjectNames");

    selectedNames = null;
    deselectedNames = new Vector<ObjectName>();

    RELATION_LOGGER.exiting(MBeanServerNotificationFilter.class.getName(),
        "enableAllObjectNames");
    return;
  }

  /**
   * Enables MBeanServerNotifications concerning given ObjectName.
   *
   * @param objectName ObjectName of interest
   * @throws IllegalArgumentException if the given ObjectName is null
   */
  public synchronized void enableObjectName(ObjectName objectName)
      throws IllegalArgumentException {

    if (objectName == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(MBeanServerNotificationFilter.class.getName(),
        "enableObjectName", objectName);

    // Removes from deselected ObjectNames, if present
    if (deselectedNames != null) {
      if (deselectedNames.size() != 0) {
        deselectedNames.remove(objectName);
      }
    }

    // Adds it in selected ObjectNames
    if (selectedNames != null) {
      // If all are selected, no need to do anything :)
      if (!(selectedNames.contains(objectName))) {
        // ObjectName was not already selected
        selectedNames.add(objectName);
      }
    }

    RELATION_LOGGER.exiting(MBeanServerNotificationFilter.class.getName(),
        "enableObjectName");
    return;
  }

  /**
   * Gets all the ObjectNames enabled.
   *
   * @return Vector of ObjectNames: <P>- null means all ObjectNames are implicitly selected, except
   * the ObjectNames explicitly deselected <P>- empty means all ObjectNames are deselected, i.e. no
   * ObjectName selected.
   */
  public synchronized Vector<ObjectName> getEnabledObjectNames() {
    if (selectedNames != null) {
      return new Vector<ObjectName>(selectedNames);
    } else {
      return null;
    }
  }

  /**
   * Gets all the ObjectNames disabled.
   *
   * @return Vector of ObjectNames: <P>- null means all ObjectNames are implicitly deselected,
   * except the ObjectNames explicitly selected <P>- empty means all ObjectNames are selected, i.e.
   * no ObjectName deselected.
   */
  public synchronized Vector<ObjectName> getDisabledObjectNames() {
    if (deselectedNames != null) {
      return new Vector<ObjectName>(deselectedNames);
    } else {
      return null;
    }
  }

  //
  // NotificationFilter interface
  //

  /**
   * Invoked before sending the specified notification to the listener.
   * <P>If:
   * <P>- the ObjectName of the concerned MBean is selected (explicitly OR
   * (implicitly and not explicitly deselected))
   * <P>AND
   * <P>- the type of the operation (registration or unregistration) is
   * selected
   * <P>then the notification is sent to the listener.
   *
   * @param notif The notification to be sent.
   * @return true if the notification has to be sent to the listener, false otherwise.
   * @throws IllegalArgumentException if null parameter
   */
  public synchronized boolean isNotificationEnabled(Notification notif)
      throws IllegalArgumentException {

    if (notif == null) {
      String excMsg = "Invalid parameter.";
      throw new IllegalArgumentException(excMsg);
    }

    RELATION_LOGGER.entering(MBeanServerNotificationFilter.class.getName(),
        "isNotificationEnabled", notif);

    // Checks the type first
    String ntfType = notif.getType();
    Vector<String> enabledTypes = getEnabledTypes();
    if (!(enabledTypes.contains(ntfType))) {
      RELATION_LOGGER.logp(Level.FINER,
          MBeanServerNotificationFilter.class.getName(),
          "isNotificationEnabled",
          "Type not selected, exiting");
      return false;
    }

    // We have a MBeanServerNotification: downcasts it
    MBeanServerNotification mbsNtf = (MBeanServerNotification) notif;

    // Checks the ObjectName
    ObjectName objName = mbsNtf.getMBeanName();
    // Is it selected?
    boolean isSelectedFlg = false;
    if (selectedNames != null) {
      // Not all are implicitly selected:
      // checks for explicit selection
      if (selectedNames.size() == 0) {
        // All are explicitly not selected
        RELATION_LOGGER.logp(Level.FINER,
            MBeanServerNotificationFilter.class.getName(),
            "isNotificationEnabled",
            "No ObjectNames selected, exiting");
        return false;
      }

      isSelectedFlg = selectedNames.contains(objName);
      if (!isSelectedFlg) {
        // Not in the explicit selected list
        RELATION_LOGGER.logp(Level.FINER,
            MBeanServerNotificationFilter.class.getName(),
            "isNotificationEnabled",
            "ObjectName not in selected list, exiting");
        return false;
      }
    }

    if (!isSelectedFlg) {
      // Not explicitly selected: is it deselected?

      if (deselectedNames == null) {
        // All are implicitly deselected and it is not explicitly
        // selected
        RELATION_LOGGER.logp(Level.FINER,
            MBeanServerNotificationFilter.class.getName(),
            "isNotificationEnabled",
            "ObjectName not selected, and all " +
                "names deselected, exiting");
        return false;

      } else if (deselectedNames.contains(objName)) {
        // Explicitly deselected
        RELATION_LOGGER.logp(Level.FINER,
            MBeanServerNotificationFilter.class.getName(),
            "isNotificationEnabled",
            "ObjectName explicitly not selected, exiting");
        return false;
      }
    }

    RELATION_LOGGER.logp(Level.FINER,
        MBeanServerNotificationFilter.class.getName(),
        "isNotificationEnabled",
        "ObjectName selected, exiting");
    return true;
  }


  /**
   * Deserializes an {@link MBeanServerNotificationFilter} from an {@link ObjectInputStream}.
   */
  private void readObject(ObjectInputStream in)
      throws IOException, ClassNotFoundException {
    if (compat) {
      // Read an object serialized in the old serial form
      //
      ObjectInputStream.GetField fields = in.readFields();
      selectedNames = cast(fields.get("mySelectObjNameList", null));
      if (fields.defaulted("mySelectObjNameList")) {
        throw new NullPointerException("mySelectObjNameList");
      }
      deselectedNames = cast(fields.get("myDeselectObjNameList", null));
      if (fields.defaulted("myDeselectObjNameList")) {
        throw new NullPointerException("myDeselectObjNameList");
      }
    } else {
      // Read an object serialized in the new serial form
      //
      in.defaultReadObject();
    }
  }


  /**
   * Serializes an {@link MBeanServerNotificationFilter} to an {@link ObjectOutputStream}.
   */
  private void writeObject(ObjectOutputStream out)
      throws IOException {
    if (compat) {
      // Serializes this instance in the old serial form
      //
      ObjectOutputStream.PutField fields = out.putFields();
      fields.put("mySelectObjNameList", selectedNames);
      fields.put("myDeselectObjNameList", deselectedNames);
      out.writeFields();
    } else {
      // Serializes this instance in the new serial form
      //
      out.defaultWriteObject();
    }
  }
}
